/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "spatial_coordinate_system.h"

#include <cstdlib>
#include <cmath>
#include <random>
#include "constants.h"
#include "hilog_wrapper.h"
#include "spatial_awareness_mgr_service.h"

namespace OHOS {
namespace Msdp {
namespace {
auto g_service = DelayedSpSingleton<SpatialaWarenessMgrService>::GetInstance();
}
SpatialCoordinateSystem::SpatialCoordinateSystem()
{
    SPATIAL_LOGI(MODULE_SERVICE, "SpatialCoordinateSystem Create");
}

SpatialCoordinateSystem::~SpatialCoordinateSystem()
{
    SPATIAL_LOGI(MODULE_SERVICE, "SpatialCoordinateSystem Destory");
    auto algoInstMap = g_service->GetAlgorithmInstMap();
    auto iter = algoInstMap.find(AlgorithmType::COMMON);
    if (iter != algoInstMap.end()) {
        iter->second->Disable();
    }
}

void SpatialCoordinateSystem::InsertLocalDeviceInfo(const DeviceInfo& centerDevice)
{
    map_.center = centerDevice;
    DeviceCoordinate coordinate;
    coordinate.exact = true;
    map_.map.insert(std::make_pair(centerDevice, coordinate));
    GetMockDeviceInfo();
    map_.commitTime = 0;
}

void SpatialCoordinateSystem::GetMockDeviceInfo()
{
}

void SpatialCoordinateSystem::Init()
{
    SPATIAL_LOGI(MODULE_SERVICE, "SpatialCoordinateSystem Init");
    auto algoInstMap = g_service->GetAlgorithmInstMap();
    auto iter = algoInstMap.find(AlgorithmType::COMMON);
    if (iter != algoInstMap.end()) {
        std::shared_ptr<SpatialCallback> callback = shared_from_this();
        iter->second->Enable(callback);
    }
}

void SpatialCoordinateSystem::UpdateCenterNetworkId(const std::string& networkId)
{
    SPATIAL_LOGI(MODULE_SERVICE, "SpatialCoordinateSystem UpdateCenterNetworkId");
    map_.center.networkId = networkId;
}

void SpatialCoordinateSystem::RegisterUpdateListener(const std::shared_ptr<UpdateListener> &listener)
{
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s enter", __func__);
    std::lock_guard<std::mutex> lock(mutex_);
    listeners_.push_back(listener);
}

void SpatialCoordinateSystem::UnRegsterUpdateLister(const std::shared_ptr<UpdateListener> &listener)
{
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s enter", __func__);
    std::lock_guard<std::mutex> lock(mutex_);
    if (listeners_.empty()) {
        SPATIAL_LOGE(MODULE_SERVICE, "%{public}s listeners_ is empty()", __func__);
        return;
    }
    listeners_.remove(listener);
}

bool SpatialCoordinateSystem::IsEmpty()
{
    if (map_.map.size() <= 1) {
        return true;
    }
    return false;
}

bool SpatialCoordinateSystem::IsExistedDevice(const DeviceInfo& device)
{
    std::lock_guard<std::mutex> lock(mutex_);
    auto coordinate = map_.map.find(device);
    if (coordinate != map_.map.end()) {
        return true;
    }
    return false;
}

bool SpatialCoordinateSystem::UpdateDevices(const std::vector<DeviceInfo>& devices,
    bool newDeviceOnly)
{
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s Add devices", __func__);
    std::lock_guard<std::mutex> lock(mutex_);

    if (devices.empty()) {
        SPATIAL_LOGE(MODULE_SERVICE, "%{public}s updated device list is null", __func__);
        return false;
    }

    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s  deviceMap size= %{public}d", __func__, map_.map.size());
    std::vector<SpatialDevice> newDevices;
    for (auto device = devices.begin(); device != devices.end(); device++) {
        auto coordinate = map_.map.find(*device);
        if (coordinate == map_.map.end() || !newDeviceOnly) {
            SPATIAL_LOGI(MODULE_SERVICE,
                "Add new device: %{public}s", device->deviceId.c_str());
            SpatialDevice newDevice;
            newDevices.push_back(device->ToSpatialDevice());
        }
    }

    if (newDevices.empty()) {
        SPATIAL_LOGE(MODULE_SERVICE, "%{public}s new device list is null", __func__);
        return false;
    }

    calculating_ = true;

    auto algoInstMap = g_service->GetAlgorithmInstMap();
    auto iter = algoInstMap.find(AlgorithmType::COMMON);
    if (iter != algoInstMap.end()) {
        auto capability = iter->second->GetCapability();
        if (capability.mediumMap_[TransmissionMedium::WIFI] == MediumPriority::HIGH) {
            iter->second->Trigger(map_.center.ToSpatialDevice(), newDevices);
        }
    }
    return true;
}

bool SpatialCoordinateSystem::MergeMap(const std::shared_ptr<SpatialMap>& updateMap)
{
    SPATIAL_LOGI(MODULE_SERVICE,
        "MergeMap:(%{public}d vs %{public}d)", map_.top, updateMap->top);
    if (calculating_) {
        SPATIAL_LOGI(MODULE_SERVICE, "map is calculating");
        return false;
    }
    if (!updateMap->committed) {
        SPATIAL_LOGI(MODULE_SERVICE, "MergeMap is not committed");
        return false;
    }
    if (map_ == (*updateMap.get())) {
        SPATIAL_LOGI(MODULE_SERVICE, "MergeMap Same map");
        return false;
    }
    for (auto iter = map_.history.begin(); iter != map_.history.end(); iter++) {
        if (updateMap->top == iter->first) {
            SPATIAL_LOGI(MODULE_SERVICE, "MergeMap already have the map in history");
            return false;
        }
    }

    auto offset = map_.map.find(updateMap->center);
    if (offset != map_.map.end()) {
        UpdateByMap(updateMap, offset->second);
        return true;
    }
    std::vector<SpatialDevice> spatialDeviceList;
    spatialDeviceList.push_back(updateMap->center.ToSpatialDevice());
    if (spatialDeviceList.empty()) {
        return false;
    }

    auto algoInstMap = g_service->GetAlgorithmInstMap();
    auto iter = algoInstMap.find(AlgorithmType::COMMON);
    if (iter != algoInstMap.end()) {
        auto capability = iter->second->GetCapability();
        if (capability.mediumMap_[TransmissionMedium::WIFI] == MediumPriority::HIGH &&
            iter->second->Trigger(map_.center.ToSpatialDevice(), spatialDeviceList)) {
            SPATIAL_LOGI(MODULE_SERVICE, "start to trigger spatial event");
            eventActions_.push_back([this, updateMap](const SpatialEvent& event) {
                DeviceInfo device(event.device);
                if (device == updateMap->center) {
                    this->UpdateByMap(updateMap, DeviceCoordinate(event, GetOrientation()));
                    return true;
                }
                return false;
            });
        }
    }
    return true;
}

bool SpatialCoordinateSystem::SyncMap(const std::shared_ptr<SpatialMap>& updateMap)
{
    SPATIAL_LOGI(MODULE_SERVICE, "SpatialCoordinateSystem updateMap");
    std::lock_guard<std::mutex> lock(mutex_);

    if (calculating_) {
        SPATIAL_LOGI(MODULE_SERVICE, "map is calculating");
        return false;
    }
    auto commit = updateMap->history.find(map_.top);
    if (commit == updateMap->history.end()) {
        SPATIAL_LOGI(MODULE_SERVICE,
            "Update map doesn't include current top, can't sync");
        return false;
    }
    if (map_.committed && updateMap->committed) {
        map_.map.clear();
        map_.history.clear();
        map_ = *updateMap.get();
        map_.updateTime = time(0);
    } else {
        SPATIAL_LOGW(MODULE_SERVICE, "Not committed conntent exists, can't sync");
        return false;
    }
    return true;
}

std::shared_ptr<SpatialMap> SpatialCoordinateSystem::Commit()
{
    SPATIAL_LOGW(MODULE_SERVICE, "SpatialCoordinateSystem Commit");
    std::lock_guard<std::mutex> lock(mutex_);
    int min = 0;
    int max = 100000;
    std::default_random_engine engine;
    std::uniform_int_distribution<unsigned> distrib(min, max);
    time_t t;
    if (map_.committed) {
        SPATIAL_LOGW(MODULE_SERVICE, "SpatialCoordinateSystem has committed");
        return std::make_shared<SpatialMap>(map_);
    }
    (void)engine.seed(time(&t));
    map_.top = distrib(engine);
    SPATIAL_LOGW(MODULE_SERVICE, "top: %{public}d", map_.top);
    map_.history.emplace(map_.top, t);
    for (auto iter = map_.map.begin(); iter != map_.map.end(); iter++) {
        iter->second.keepingTime = t - iter->second.updateTime;
    }
    map_.committed = true;
    map_.commitTime = t;
    std::shared_ptr<SpatialMap> map = std::make_shared<SpatialMap>(map_);
    SPATIAL_LOGW(MODULE_SERVICE, "SpatialCoordinateSystem return");
    return map;
}

uint32_t SpatialCoordinateSystem::GetTopCommit()
{
    SPATIAL_LOGW(MODULE_SERVICE, "GetTopCommit: %{public}d", map_.top);
    return map_.top;
}

bool SpatialCoordinateSystem::IsInHistory(uint32_t commit)
{
    auto iter = map_.history.find(commit);
    if (iter != map_.history.end()) {
        return true;
    }
    return false;
}

float SpatialCoordinateSystem::GetOrientation()
{
    // to add implement
    SPATIAL_LOGW(MODULE_SERVICE, "SpatialCoordinateSystem GetOrientation");
    return 0;
}

uint32_t CalculateDirection(float angle)
{
    SPATIAL_LOGW(MODULE_SERVICE, "CalculateDirection %{public}f", angle);
    int angleOne = 45;
    int angleTwo = 135;
    int angleThree = 225;
    int angleFour = 315;
    int angleEnd = 360;
    int angleStart = 0;
    int invalidOne = -1;
    int invalidTwo = -2;
    if (angle >= angleOne && angle < angleTwo) {
        return SpatialDirection::FRONT;
    } else if (angle >= angleTwo && angle < angleThree) {
        return SpatialDirection::LEFT;
    } else if (angle >= angleThree && angle < angleFour) {
        return SpatialDirection::BACK;
    } else if ((angle >= angleStart && angle <= angleOne) && (angle >= angleFour && angle <= angleEnd)) {
        return SpatialDirection::RIGHT;
    } else if (angle == invalidOne) {
        return SpatialDirection::LEFT;
    } else if (angle == invalidTwo) {
        return SpatialDirection::RIGHT;
    } else {
        return SpatialDirection::INVALID;
    }
}

std::shared_ptr<DeviceCoordinate> SpatialCoordinateSystem::GetCoordinate(
    const DeviceInfo& device, bool& success)
{
    SPATIAL_LOGW(MODULE_SERVICE, "GetCoordinate %{public}s", device.deviceId.c_str());
    std::lock_guard<std::mutex> lock(mutex_);

    auto iter = map_.map.find(device);
    if (iter != map_.map.end()) {
        success = false;
        return std::make_shared<DeviceCoordinate>();
    }
    DeviceCoordinate& coordinate = iter->second;
    if (coordinate.exact) {
        coordinate.angle = coordinate.angle - GetOrientation();
        if (coordinate.direction != SpatialDirection::INSIDE) {
            coordinate.direction = CalculateDirection(coordinate.angle);
        }
    }
    coordinate.keepingTime = time(0) - coordinate.updateTime;
    success = true;
    return std::make_shared<DeviceCoordinate>(coordinate);
}

void SpatialCoordinateSystem::OnResult(const SpatialEvent& event)
{
    DumpSpatialEvent(event);
    event_.device = event.device;
    CheckSpatialEvent(event);
    if (!event_.IsExact()) {
        SPATIAL_LOGE(MODULE_SERVICE, "distance angle not exist");
    } else {
        DumpSpatialEvent(event_);
        this->UpdateByEvent(event_);
        auto action = eventActions_.begin();
        SPATIAL_LOGI(MODULE_SERVICE,
            "onResult SpatialEvent: start execute event action");
        while (action != eventActions_.end()) {
            bool ret = (*action)(event);
            if (ret) {
                auto used = action;
                eventActions_.erase(used);
            }
            action++;
        }
        SPATIAL_LOGI(MODULE_SERVICE,
            "onResult SpatialEvent return");
    }
}
void SpatialCoordinateSystem::DumpSpatialEvent(const SpatialEvent& event)
{
    SPATIAL_LOGI(MODULE_SERVICE,
        "%{public}s deviceId %{public}s, deviceName:%{public}s, deviceType:%{public}d", __func__,
        event.device.deviceId.c_str(), event.device.deviceName.c_str(), event.device.typeId);
    SPATIAL_LOGI(MODULE_SERVICE,
        "%{public}s OnResult direction: %{public}d, distance: %{public}f, angle: %{public}f"
        "exist: %{public}d, innerouter: %{public}d", __func__, event.direction,
        event.distance, event.angle, event.exist, event.innerOuter);
}

void SpatialCoordinateSystem::DumpSpatialMap(const SpatialMap& spatialMap)
{
    if (spatialMap.map.empty()) {
        return;
    }
    for (auto &iter : spatialMap.map) {
        SPATIAL_LOGI(MODULE_SERVICE, "deviceId: %{public}s", iter.first.deviceId.c_str());
        SPATIAL_LOGI(MODULE_SERVICE, "distance: %{public}f, angle: %{public}f, direction: %{public}d",
            iter.second.distance, iter.second.angle, iter.second.direction);
    }
}

void SpatialCoordinateSystem::CheckSpatialEvent(const SpatialEvent& event)
{
    SPATIAL_LOGI(MODULE_SERVICE, "enter");

    if (event.angle > INVALID_DATA) {
        event_.angle = event.angle;
    }

    if (event.distance > INVALID_DATA) {
        event_.distance = event.distance;
    }

    if (event.direction > INVALID_DATA) {
        event_.direction = event.direction;
    }
}

void SpatialCoordinateSystem::OnEnd()
{
    SPATIAL_LOGI(MODULE_SERVICE, "OnEnd enter listeners_.size: %{public}zu", listeners_.size());
    calculating_ = false;
    if (listeners_.empty()) {
        SPATIAL_LOGE(MODULE_SERVICE, "session listeners is empty");
        return;
    }

    for (auto &listener : listeners_) {
        if (listener == nullptr) {
            continue;
        }
        SPATIAL_LOGI(MODULE_SERVICE, "OnUpdateEnd start");
        listener->OnUpdateEnd();
    }
}

void SpatialCoordinateSystem::UpdateByEvent(const SpatialEvent& event)
{
    SPATIAL_LOGI(MODULE_SERVICE, "UpdateByEvent:%{public}s", event.device.deviceId.c_str());
    DeviceInfo device(event.device);
    if (!event.exist) {
        SPATIAL_LOGI(MODULE_SERVICE, "remove device %{public}s", event.device.deviceId.c_str());
        if (map_.map.find(device) == map_.map.end()) {
            map_.map.erase(device);
        }
        for (auto listener = listeners_.begin(); listener != listeners_.end(); listener++) {
            (*listener)->OnDeviceRemoved(device);
        }
        return;
    }
    SPATIAL_LOGI(MODULE_SERVICE, "UpdateByEvent: find device by event");
    DumpSpatialEvent(event);
    auto iter = map_.map.find(device);
    if (iter == map_.map.end()) {
        SPATIAL_LOGI(MODULE_SERVICE, "Add deviceId=%{public}s", event.device.deviceId.c_str());
        DeviceCoordinate coordinate = DeviceCoordinate(event, GetOrientation());
        map_.map.insert(std::make_pair(device, coordinate));
        DumpSpatialMap(map_);
        map_.committed = false;
        map_.updateTime = time(0);
        for (auto listener = listeners_.begin(); listener != listeners_.end(); listener++) {
            (*listener)->OnDeviceAdded(device, coordinate);
        }
    } else if (event.IsExact() || !iter->second.exact) {
        SPATIAL_LOGI(MODULE_SERVICE, "found device %{public}s", event.device.deviceId.c_str());
        iter->second = DeviceCoordinate(event, GetOrientation());
        map_.map[device] = iter->second;
        map_.committed = false;
        map_.updateTime = time(0);
        SPATIAL_LOGI(MODULE_SERVICE, "Coordinate monitoring numbers %{public}zu", listeners_.size());
        for (auto listener = listeners_.begin(); listener != listeners_.end(); listener++) {
            (*listener)->OnDeviceCoordinateUpdate(device, iter->second);
        }
    } else {
        SPATIAL_LOGI(MODULE_SERVICE,
            "Don't Update not exact coordinate for: %{public}s", event.device.deviceId.c_str());
    }
    SPATIAL_LOGI(MODULE_SERVICE, "UpdateByEvent: return");
}

void SpatialCoordinateSystem::UpdateByMap(const std::shared_ptr<SpatialMap>& updateMap,
    const DeviceCoordinate& offset)
{
    std::lock_guard<std::mutex> lock(mutex_);
    if (!offset.exact) {
        SPATIAL_LOGE(MODULE_SERVICE, "UpdateByMap offset center doesn't have exact coordinate");
        return;
    }
    for (auto iter = updateMap->map.begin(); iter != updateMap->map.end(); iter++) {
        auto mapIter = map_.map.find(iter->first);
        if (mapIter == map_.map.end()) {
            SPATIAL_LOGI(MODULE_SERVICE,
                "add device: %{public}s", iter->first.deviceId.c_str());
            DeviceCoordinate coordinate = iter->second;
            OnDeviceAdd(iter->first, coordinate, offset);
        } else if (mapIter->second.keepingTime > iter->second.keepingTime) {
            SPATIAL_LOGI(MODULE_SERVICE,
                "update device: %{public}s", iter->first.deviceId.c_str());
            DeviceCoordinate coordinate = iter->second;
            OnCoordinateUpdate(iter->first, coordinate, offset, mapIter->second);
        } else {
            SPATIAL_LOGI(MODULE_SERVICE,
                "skip device: %{public}s", mapIter->first.deviceId.c_str());
        }
    }
    MergeHistory(updateMap);
    map_.committed = false;
    map_.updateTime = time(0);
    for (auto listener = listeners_.begin(); listener != listeners_.end(); listener++) {
        (*listener)->OnUpdateEnd();
    }
}

void SpatialCoordinateSystem::OnDeviceAdd(DeviceInfo deviceInfo,
    DeviceCoordinate& coordinate, const DeviceCoordinate& offset)
{
    if (TransformCoordinate(coordinate, offset)) {
        map_.map.insert(std::make_pair(deviceInfo, coordinate));
        for (auto listener = listeners_.begin(); listener != listeners_.end(); listener++) {
            (*listener)->OnDeviceAdded(deviceInfo, coordinate);
        }
    }
}

void SpatialCoordinateSystem::OnCoordinateUpdate(DeviceInfo deviceInfo,
    DeviceCoordinate& coordinate, const DeviceCoordinate& offset, DeviceCoordinate& coordinateMap)
{
    if (TransformCoordinate(coordinate, offset)) {
        coordinateMap = coordinate;
        for (auto listener = listeners_.begin(); listener != listeners_.end(); listener++) {
            (*listener)->OnDeviceCoordinateUpdate(deviceInfo, coordinate);
        }
    }
}

void SpatialCoordinateSystem::MergeHistory(const std::shared_ptr<SpatialMap>& updateMap)
{
    for (auto iter = updateMap->history.begin(); iter != updateMap->history.end(); iter++) {
        map_.history.insert(*iter);
    }
    map_.top = updateMap->top;
}

bool SpatialCoordinateSystem::TransformCoordinate(
    DeviceCoordinate& coordinate,
    const DeviceCoordinate& offset)
{
    int square = 2;
    if (!offset.exact || !coordinate.exact) {
        return false;
    }
    coordinate.x = coordinate.x + offset.x;
    coordinate.y = coordinate.y + offset.y;
    coordinate.distance = sqrt(pow(coordinate.x, square) + pow(coordinate.y, square));
    coordinate.angle = acos(coordinate.x / coordinate.distance);
    coordinate.updateTime = time(0) - coordinate.keepingTime;
    return true;
}

SpatialMap SpatialCoordinateSystem::GetSpatialMap()
{
    return map_;
}
} // namespace Msdp
} // namespace OHOS